/** Part of Zombie Navigator
	Copyright © 2016  Zombie Navigator Developers
	
	@file
	@module
	@license AGPL-3.0+
	
*/ 'use strict';


/* Addon SDK dependencies */
const _sdkTabs = require('sdk/tabs');
const _sdkTimers = require('sdk/timers');
const _sdkPageMod = require('sdk/page-mod'); // Addon UI
const {isPrivate: _sdkIsPrivate} = require('sdk/private-browsing');
const {viewFor: _sdkViewFor} = require('sdk/view/core'); // XXX: Low-level API

/* Used modules */
const _pMeta = require('./meta');
const _pOptions = require('./options');
const _pLogger = require('./logger');
const _iContentTabs = require('./internal/content-tabs');

const _uiLibDataPath = 'ui/ui.js';

// null is evil: 'object' == typeof null
const _noNull = a => null === a ? void(0) : a;
const _gOPN = Object.getOwnPropertyNames.bind(Object);

/** @private */
const _tabUtils = (sdkTab, sdkWorker) => {
	const sdkIsPrivate = _sdkIsPrivate;
	const iContentTabs = _iContentTabs;
	
	const closeHandlers = [];
	sdkTab.on('close', () => void(closeHandlers.forEach(f => f())));
	
	let detached = false;
	sdkWorker && sdkWorker.on('detach', () => {
		detached = true;
	});
	
	const contentTabs = iContentTabs.getTabs(sdkTab.window);
	
	return Object.freeze({__proto__: null
		,sendMessage (m) {
			if (detached) return false;
			if (!sdkWorker) return false;
			sdkWorker.port.emit('uiMsg', m);
			return true;
		}
		,onClose (f) {
			closeHandlers.push(f);
		}
		,closeAll () {
			
		}
		,tabs: contentTabs // hosts unprivileged pages
		,get scriptDetached () {
			return !!detached;
		}
		,get isPrivate () {
			return !!sdkIsPrivate(sdkTab);
		}
		,get _sdkWindow () { // XXX: To be removed before beta
			return sdkTab.window;
		}
	});
};


/* Internal APIs */

/**
	Open an addon UI in a new window
	(Please don't open a remote or user-supplied page with this)
	@public
	@method
	@param {string} uri
	@param {boolean} isPrivate
	@param {string} uiScriptDataPath
	@param {object} acceptCommands
	@returns {boolean}
*/
const openPopup = (uri, isPrivate, uiScriptDataPath, acceptors) => {
	const sdkTabs = _sdkTabs, {setTimeout} = _sdkTimers;
	const pMeta = _pMeta, pOptions = _pOptions, pLogger = _pLogger;
	const activeUI = pOptions.activeUiEnabled();
	const uiLibURI = pMeta.getURIFromDataPath(_uiLibDataPath);
	const uiScriptURI = uiScriptDataPath
		? pMeta.getURIFromDataPath(uiScriptDataPath) : void(0);
	
	const sdkTabOptions = {}; // const but not frozen
	sdkTabOptions.url = uri ? uri + '' : 'about:blank';
	sdkTabOptions.isPrivate = !!isPrivate;
	sdkTabOptions.inNewWindow = true;
	acceptors = activeUI ? _noNull(acceptors) : void 0;
	
	const acceptorNames = 'object' == typeof acceptors ? (
		Object.freeze(acceptors), _gOPN(acceptors)
	) : [];
	
	// content script destroyed on 'ready' on ESR38
	return new Promise((resolve, reject) => {
		sdkTabOptions.onOpen = sdkTab => void(sdkTab.once('ready', sdkTab => {
			if (pOptions.activeUiEnabled()) try {
				const sdkViewFor = _sdkViewFor;
				const initialURI = sdkTab.url;
				
				// UX: Indicate that the UI is not from a remote site, visually
				const showIdentity = sdkTab => {try {
					if (initialURI !== sdkTab.url) return; // URI changed
					
					// This does work with e10s
					const $id = id =>
						sdkViewFor(sdkTab.window).document.getElementById(id);
					const idBox = $id('identity-box');
					const idLabel = $id('identity-icon-label');
					
					// Don't pretend to be a browser UI or an HTTPS-protected page
					//idBox.className = 'chromeUI';
					idBox.tooltipText = '';
					idLabel.value = 'Zombie Navigator';
					idLabel.parentNode.collapsed = false;
				} catch (e) {
					pLogger.debug('exception:', e);
				}};
				
				// a bit tricky
				const detectOnLoad = sdkTab => sdkTab.attach({
					contentScript: `
if ('complete' == document.readyState) self.postMessage(true);
else document.addEventListener('load', ev => void(self.postMessage(true)));
					`
					,onMessage (msg) {
						showIdentity(sdkTab);
					}
				});
				//setTimeout(() => void(showIdentity(sdkTab)), 100);
				
				detectOnLoad(sdkTab);
				sdkTab.on('ready', detectOnLoad);
				sdkTab.on('pageshow', detectOnLoad);
				sdkTab.on('activate', showIdentity);
			} catch (e) {
				pLogger.debug('exception:', e);
			}
			
			try {
				const worker = uiScriptURI ? sdkTab.attach({
					contentScriptFile: [uiLibURI, uiScriptURI]
					,contentScriptOptions: {activeUI: (!!activeUI)}
				}) : void 0;
				
				if (worker && acceptorNames.length) {
					worker.port.on('uiComm', m => {
						const {id, data} = m;
						if (0 > acceptorNames.indexOf(id)) return;
						if ('function' != typeof acceptors[id]) return;
						
						try {
							acceptors[id](data);
						} catch (e) {
							pLogger.debug('exception:', e);
						}
					});
				}
				
				resolve(_tabUtils(sdkTab, worker));
			} catch (e) {
				reject(e);
			}
		}));
		
		try {
			sdkTabs.open(sdkTabOptions);
		} catch (e) {
			reject(e);
		}
	});
};

/** @public
	@method */
const openPreferences = isPrivate => {
	const sdkTabs = _sdkTabs;
	const {getProductID} = _pMeta, pOptions = _pOptions;
	
	if (!pOptions.activeUiEnabled()) return;
	
	sdkTabs.open({
		url: 'about:addons'
		,isPrivate: !!isPrivate
		,onOpen: sdkTab => void(sdkTab.once('ready', sdkTab => {
			const addonID = getProductID();
			
			// Trivial static contentScript on about:addons
			const worker = sdkTab.attach({
				contentScript: `
(() => {
	'use strict';
	
	self.port.on('open-prefs', addonID => {
		AddonManager.getAddonByID(addonID, addon => {
			unsafeWindow.gViewController.commands.cmd_showItemDetails
				.doCommand(addon, true);
		});
	});
	
	self.postMessage(true); // Done!
})();
`
				// end of contentScript
				,onMessage: () => worker.port.emit('open-prefs', addonID)
			});
		}))
	});
};

const registerScriptedUI = (pageDataPath, scriptDataPath, acceptors) => {
	const sdkPageMod = _sdkPageMod;
	const pMeta = _pMeta, pOptions = _pOptions;
	
	if (!pOptions.activeUiEnabled()) return false;
	
	[pageDataPath, scriptDataPath] = [pageDataPath, scriptDataPath].map(dp => dp + '');
	[pageDataPath, scriptDataPath]
	.forEach(dp => ['*', '..', '//'].forEach(bad => {
		if (-1 != dp.indexOf(bad)) throw new TypeError('Invalid data path');
	}));
	
	acceptors = _noNull(acceptors);
	const acceptorNames = 'object' == typeof acceptors ? (
		Object.freeze(acceptors), _gOPN(acceptors)
	) : [];
	
	try {
		const [pageURI, scriptURI, libURI]
			= [pageDataPath, scriptDataPath, _uiLibDataPath]
			.map(dp => pMeta.getURIFromDataPath(dp));
		
		sdkPageMod.PageMod({
			include: pageURI
			,contentScriptFile: [libURI, scriptURI]
			,onAttach (sdkWorker) {
				let detached = false;
				sdkWorker.on('detach', () => void(detached = true));
				
				if (acceptorNames.length) {
					sdkWorker.port.on('uiComm', m => {
						const {id, data} = m;
						if (0 > acceptorNames.indexOf(id)) return;
						if ('function' != typeof acceptors[id]) return;
						
						acceptors[id](data);
					});
				}
			}
		});
	} catch (e) {
		return false;
	}
	
	return true;
};

/* Exports */
exports.openPopup = openPopup;
exports.openPreferences = openPreferences;


// vim: ts=4 noet ai

